package cn.bonoon.kernel.menus;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import javax.servlet.ServletContext;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.web.context.ServletContextAware;

import cn.bonoon.kernel.VisibleLevel;
import cn.bonoon.kernel.VisibleScope;
import cn.bonoon.kernel.menus.privileges.ChannelPrivilegeInspector;
import cn.bonoon.kernel.menus.tips.MenuTipHandler;
import cn.bonoon.kernel.support.IOperator;
import cn.bonoon.kernel.util.MD5Util;
import cn.bonoon.kernel.util.StringHelper;

/**
 * 定义一个平台的管理器；平台包括多个子系统，平台的管理功能等
 * 
 * 目前的系统不能通过菜单直接定义多个平台；以后需要有这样的功能
 * 
 * id:平台的ID，系统启动初始化后，这个ID会被生成MD5码；使用{@link PlatformContext#idGenerate(String)} 方法来生成
 * name:系统的名字，与 {@link StyleDefinitionImpl#getName()} 相同的
 * path:当前验证系统定义的进入系统的路径，如：以"s/"开头，子系统都会自动加上这个前路径 
 * 
 * @author jackson
 *
 */
public final class StandardModuleManager extends AstractModule implements ModuleManager, InitializingBean, BeanFactoryAware, ServletContextAware {
	
	private final String DEFAULT_CHECKED_IMAGE = "/res/images/checkedBox.png";
	private final String DEFAULT_UNCHECK_IMAGE = "/res/images/notCheckBox.png";
	private final String DEFAULT_DOCUMENT_FOLDER = "helper";
	
	/** 整个系统平台的菜单管理器，在这里有时是需要访问数据库的，如：某个用户或某个子系统可以个性化设置了某些参数，
	 * 而这些参数是需要存放在数据库里的；所以这里则必须能访问数据库。 */
	@PersistenceContext
	private EntityManager entityManager;
	
	/** 登录界面的样式的定义，如：系统名、系统logo等；目前平台只有一个登录的入口，
	 * 是否每个子系统都可以有自己的登录界面？以后再考虑 */
	private LoginPageDefinitionImpl loginPage;
	
	/** Servlet的上下文，所以关于界面的处理的，都应该有Servlet的上下文，因为需要使用到web服务器运行时的信息 */
	private ServletContext servletContext;
	
	/** 本平台所使用的资源服务器，系统会根据定义来查找所需要的静态资源，如：js,css,image等静态资源 */
	private String resourceServer;
	
	/** 这个平台所使用的帮助文档所存储的位置 */
	private String documentFolder;

	/** 资源解释器，给定一个路径，可以返回具体资源所在的位置 */
	private ServerResourceResolver resourceResolver;
	
	private String defaultEndpoint;

	/** 用来缓存所有定义的子系统 */
	private final Map<String, ModuleDefinitionImpl> moduleCached = new HashMap<>(8);
	/** 对子系统进行了排序处理 */
	private final List<ModuleDefinitionImpl> sortModules;
	
	/** 用来缓存所有定义的帮助文档或说明 */
	private final Map<String, DescribeDefinitionImpl> desCached = new HashMap<>();
	
	/** 将会缓存所有读取到的菜单项的定义 */
	private final Map<String, AbstractMenuable> idCached = new HashMap<>(64);
	private final Map<String, AbstractMenuable> urlCached = new HashMap<>();
	
	/** 脏数据 */
	private final Map<String, List<AbstractMenuable>> dirtyUrl = new HashMap<>(8);
	
	/** 所有子系统里定义的tab item 项，因为根据对ID的计算，所有的{@link TabItemDefinition}是不会出现重复的 */
	private final Map<String, TabItemDefinition> tabCached = new HashMap<>();
	private final Map<String, TabsDefinition> tabsCached = new HashMap<>();

	private final Map<String, AbstractMenuable> itemCached = new HashMap<String, AbstractMenuable>(64);
	
	/** 超级管理员的操作菜单，只要是操作管理员就可以操作，没有任何的限制 */
	private final ChannelDefinitionImpl channelSuper = new ChannelDefinitionImpl(2, "SUPER", "系统管理");
	
	/** 系统管理人员的操作菜单，需要根据不同管理人员的操作权限进行分配；使用时需要验证操作权限 */
	private final ChannelDefinitionImpl channelManager = new ChannelDefinitionImpl(1, "MANAGER", "系统管理");
	
	/** 普通个人的个人菜单，需要根据分配的操作权限进行限制 */
	private final ChannelDefinitionImpl channelUser = new ChannelDefinitionImpl(0, "USER", "个人中心");
	
	/** 所有的插件的版本信息 */
	private final List<VersionDefinition> versions = new ArrayList<VersionDefinition>();
	
	/** 用于处理系统平台的菜单项的说明帮助文档的上下文 */
	private DescribeContext describeContext;
	
	private class InternalDescribeContext implements DescribeContext{
		
		private final String root;
		
		private final String downloadUrl;
		
		private final String resourceUrl;
		
		private final String videoUrl;
		
		InternalDescribeContext(ServerResourceResolver resourceResolver){
			root = servletContext.getRealPath(StringHelper.get(documentFolder, DEFAULT_DOCUMENT_FOLDER));
			downloadUrl = resourceResolver.path(DOWNLOAD_URL);
			resourceUrl = resourceResolver.path(RESOURCE_URL);
			videoUrl = resourceResolver.path(VIDEO_URL);
		}

		@Override
		public File root() {
			return new File(root);
		}
		
		public String getDownloadUrl() {
			return downloadUrl;
		}
		
		public String getResourceUrl() {
			return resourceUrl;
		}
		
		public String getVideoUrl() {
			return videoUrl;
		}
	}
	
	private class InternalPlatformContext implements PlatformContext{

		private final String imageChecked;
		private final String imageUncheck;

		private long order;
		
		InternalPlatformContext(){

			resourceResolver = new ServerResourceResolver(resourceServer, servletContext);
			
			if(null == style) style = new StyleDefinitionImpl();
			style.init(resourceResolver, StandardModuleManager.this);
			
			if(null == loginPage) loginPage = new LoginPageDefinitionImpl();
			loginPage.init(StandardModuleManager.this, style);
			
			imageChecked = resourceResolver.resource(DEFAULT_CHECKED_IMAGE);
			imageUncheck = resourceResolver.resource(DEFAULT_UNCHECK_IMAGE);
			
		}
		
		@Override
		public Query hqlQuery(String hql) {
			return entityManager.createQuery(hql);
		}
		
		@Override
		public Query sqlQuery(String sql) {
			return entityManager.createNativeQuery(sql);
		}
		
		@Override
		public DescribeContext describeContext() {
			if(null == describeContext){
				describeContext = new InternalDescribeContext(resourceResolver);
			}
			return describeContext;
		}

		@Override
		public String orderGenerate() {
			return String.valueOf(order++);
		}
		
		@Override
		public ServerResourceResolver getResourceResolver() {
			return resourceResolver;
		}
		
		@Override
		public String path(String... path){
			return resourceResolver.path(path);
		}
		
		@Override
		public String resource(String path) {
			return resourceResolver.resource(path);
		}
		
		@Override
		public void add(TabsDefinition tabs) {
			tabsCached.put(tabs.getId(), tabs);
		}
		
		@Override
		public void add(TabItemDefinition tab) {
			tabCached.put(tab.getId(), tab);
		}
		
		@Override
		public void add(AbstractMenuable item) {
			idCached.put(item.getId(), item);
		}
		
		@Override
		public void add(String href, AbstractMenuable item) {
			idCached.put(item.getId(), item);
			if(null != href){
				AbstractMenuable iiDirty = urlCached.put(href, item);
				if (null != iiDirty) {
					// 存在重复的数据
					List<AbstractMenuable> items = dirtyUrl.get(href);
					if (null == items) {
						items = new ArrayList<AbstractMenuable>(1);
						dirtyUrl.put(href, items);
					}
					items.add(item);
				}
			}
		}

		@Override
		public StyleDefinitionImpl getStyle() {
			return style;
		}

		@Override
		public String idGenerate(String sid) {
			/*
			 * 1. 这里是定义系统在启动的时间，生成的平台、菜单等的ID
			 * 2. ID在创建角色的时候，是会被存储在数据库表里，如果这里改动了方式，则会现有数据库的表会有影响
			 * 3. 目前用的是md5的方式，可以统一改成其它的方式，如：base64等
			 */
			return MD5Util.getMD5(sid);
		}
		
		@Override
		public String combine(String arg, String... args) {
			String str = arg;
			for(String it : args){
				if(StringHelper.isEmpty(it)) continue;
					
				str += "-" + it;
			}
			return str;
		}

		@Override
		public String iconCheckable(boolean checked) {
			return checked ? imageChecked : imageUncheck;
		}

		@Override
		public MenuNavigation navigateGenerate(MenuNavigation parent, ItemableDefinition item) {
			return new MenuNavigationImpl(parent, item);
		}

		@Override
		public boolean add(ParentContainer parent, DescribeDefinitionImpl item) {
			if(null != item){
				item.init(this, parent);
				desCached.put(parent.getId(), item);
				return true;
			}
			return false;
		}

		@Override
		public String xpathGenerate(String parentXpath) {
			return combine(parentXpath, orderGenerate());
		}

		@Override
		public <T> T bean(String name, Class<T> requiredType) throws BeansException {
			return beanFactory.getBean(id, requiredType);
		}

//		@Override
//		public Object generateInternalId() {
//			internalIdGenerator++;
//			return internalIdGenerator;
//		}
	}
	
	public StandardModuleManager(Collection<ModuleDefinitionImpl> modules) {
		sortModules = new ArrayList<ModuleDefinitionImpl>(modules);
		Collections.sort(sortModules);
	}

	private PlatformContext platformContext;
	private BeanFactory beanFactory;
	
	@Override
	public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
		this.beanFactory = beanFactory;
	}
	
	@Override
	public void afterPropertiesSet() throws Exception {
		
		platformContext = new InternalPlatformContext();
		
		internalId = StringHelper.get(id, "SMMID");
		id = platformContext.idGenerate(internalId);
		xpath = platformContext.orderGenerate();
		
		path = resourceResolver.path("/" + path);
		defaultEndpoint = resourceResolver.path("/");
		if(StringHelper.isNotEmpty(endpoint)){ 
			endpoint = resourceResolver.path(endpoint);
		}else{
			endpoint = path;
		}
		
		platformContext.add(this, describe);
		
		for(ModuleDefinitionImpl currentModule : sortModules){
			currentModule.init(platformContext, this);
			moduleCached.put(currentModule.getId(), currentModule);
		}
		
		//读取各个插件定义的功能
		try {
			Enumeration<URL> uris = getClass().getClassLoader().getResources("cn/bonoon/plugins.properties");
			
			while(uris.hasMoreElements()){
				try(InputStream inStream = uris.nextElement().openStream()){
					Properties ppt = new Properties();
					ppt.load(inStream);
					versions.add(new VersionDefinition(resourceResolver, ppt));
					//超级管理员的功能
					readMenus(channelSuper, "super.", ppt);
					readConfig(channelSuper, CONFIG_SUPER, ppt);
					//系统管理员的功能
					readMenus(channelManager, "manager.", ppt);
					readConfig(channelManager, CONFIG_MANAGER, ppt);
					//个人管理功能
					readMenus(channelUser, "user.", ppt);
					readConfig(channelUser, CONFIG_USER, ppt);
					
					//read parts
					String part_defs = ppt.getProperty("part.definitions");
					if(StringHelper.isNotEmpty(part_defs)){
						for(String part : part_defs.split(",")){
							if(StringHelper.isEmpty(part)){ continue; }
							
							String pre = "part." + part, name = ppt.getProperty(pre + ".name");
							if(StringHelper.isEmpty(name)){ continue; }
							
							String url = ppt.getProperty(pre + ".url");
							if("true".equalsIgnoreCase(ppt.getProperty(pre + ".all"))){
								if(StringHelper.isEmpty(url)){
									url = "/s/sys/{key}!" + part;
								}else if(url.indexOf("{key}") == -1){
									url += "/{key}!" + part;
								}
								
								for(ModuleDefinitionImpl mi : sortModules){
									mi.parts.add(internalParsePart(ppt, pre, name, url));
								}
							}else{
								if(StringHelper.isEmpty(url)){
									url = "/s/sys/" + part;//相对路径
								}
							}
							parts.add(internalParsePart(ppt, pre, name, url));
						}
					}
				}
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		init(parts, id, resourceResolver);
		
		for(ModuleDefinitionImpl mi : sortModules){
			init(mi.parts, mi.getId(), resourceResolver);
		}
		
		final ModuleContext moduleContext = new ModuleContext(){

			@Override
			public ModuleDefinition getModule() {
				return StandardModuleManager.this;
			}
			
			@Override
			public void add(String id, AbstractMenuable menuable) {
				itemCached.put(id, menuable);
			}

			@Override
			public void add(MenuTipHandler handler) {
				// TODO Auto-generated method stub
				
			}
			
		};
		
		channelSuper.init(platformContext, moduleContext, this);//sourceId, PathUtil.path(path, "sys"));
		
		channelManager.init(platformContext, moduleContext, this);//sourceId, PathUtil.path(path, "mgr"));
		channelUser.setHref(resourceResolver.path("/s/user/settings.do"));
		channelUser.init(platformContext, moduleContext, this);//sourceId, path);
	}

	void init(List<PartDefinition> parts, String id, ServerResourceResolver resourceResolver){
		
		for(PartDefinition part : parts){
			String piurl = part.getUrl();
			if(piurl.startsWith("/")){
				piurl = resourceResolver.path(piurl);
			}
			part.setUrl(piurl.replace("{key}", id));
		}
		
		Collections.sort(parts);
	}
	
	private PartDefinition internalParsePart(Properties ppt, String pre, String name, String url) {
		PartDefinition pi = new PartDefinition(name, url);
		pi.setIconCls(ppt.getProperty(pre + ".iconCls"));
		//对level的解析可能要复杂得多
		pi.setLevel(VisibleLevel.valueOf(ppt.getProperty(pre + ".level")));
		pi.setOrdinal(internalParseOrdinal(ppt, pre));
		pi.setHelper(StringHelper.toboolean(ppt.getProperty(pre + ".helper")));
		return pi;
	}
	
	private void readConfig(ChannelDefinitionImpl channel, String type, Properties ppt) throws Exception{
		String fns = ppt.getProperty(type + "definitions");
		if(StringHelper.isNotEmpty(fns)){
			String[] tmpFns = fns.split(",");
			if(tmpFns.length > 0){
				TabsDefinition ti = new TabsDefinition(channel);
				ti.setId(type);
				ti.setName(ppt.getProperty(type + "name"));
				ti.setOrdinal(internalParseOrdinal(ppt, type));
				
				for(String fn : tmpFns){
					if(StringHelper.isEmpty(fn)){ continue; }
					
					String path = type + fn;
					String clazz = ppt.getProperty(path + ".class");
					if(StringHelper.isEmpty(clazz)){ continue; }
					
					TabItemClassParser ctp = new TabItemClassParser(Class.forName(clazz), ppt.getProperty(path + ".scope"));
					TabItemDefinition it = new TabItemDefinition(internalParseOrdinal(ppt, path), path, ppt.getProperty(path + ".name"), ti, ctp);
					ti.add(it);
				}
				
				channel.add(ti);
			}
		}
	}
	
	private void readMenus(ChannelDefinitionImpl channel, String type, Properties ppt){
		String fns = ppt.getProperty(type + "definitions");
		if(StringHelper.isNotEmpty(fns)){
			for(String fn : fns.split(",")){
				if(StringHelper.isEmpty(fn)){ continue; }
				
				String path = type + fn;
				String href = ppt.getProperty(path + ".href");
				if(StringHelper.isEmpty(href)){ href = fn; }
				
				MenuDefinitionImpl sm = new MenuDefinitionImpl();
				sm.setOrdinal(internalParseOrdinal(ppt, path));
				sm.setId(path);
				sm.setName(ppt.getProperty(path + ".name"));
//				sm.setKey(fn);
				sm.setHref(href);
				sm.setScope(VisibleScope.NULL);
				channel.add(sm);
			}
		}
	}
	
	private int internalParseOrdinal(Properties ppt, String name){
		try{
			return Integer.parseInt(ppt.getProperty(name + ".ordinal"));
		}catch(Exception ex){
			return 50;
		}
	}
	
	@Override
	public AbstractMenuable find(UrlRequest urlRequest) {
		// 尝试用id
		AbstractMenuable item = idCached.get(urlRequest.getMid());
		if (null != item){
//			if(item.checkNavigationUrl(urlRequest)){
//				/*
//				 * 判断下访问的URL是否在菜单里定义的ID一致。
//				 * 如果不一致，则表示可能是用户通过修改URL的ID参数来访问。
//				 */
//			}
			return item;
		}else{
			// 尝试用url
			String url = urlRequest.getDefinedUrl();
			item = urlCached.get(url);
			if (null != item) {
				urlRequest.resetUrl(item);
				// 如果是直接通过URL访问的，则需要判断该URL被定义菜单项是否有访问的权限。
				return item;
//				if (privilege.hasPrivilege(item)) {
//				}
				/*
				 * 如果当前用户没有访问该路径的权限，则该路径可能被多次进行定义。 所以需要在脏连接里查看，看是否存在多定义的情况
				 */
	//			List<ItemInfo> items = dirtyUrl.get(url);
	//			if (null != items) {
	//				for (ItemInfo it : items) {
	//					if (privilege.hasPrivilege(it)) {
	//						urlRequest.resetUrl(item);
	//						return it;
	//					}
	//				}
	//			}
			}
		}
		// 没有其它方法可以查找了，直接使用访问的路径来处理
		return null;
	}

	@Override
	public String targetUrl(String id) {
		String k = id;
		ModuleDefinitionImpl mi = moduleCached.get(k);
		if (null != mi){
			k = mi.getEndpoint();
		}
		AbstractMenuable item = idCached.get(k);
		if(null != item){
			return item.getHref();
		}
		return "";
	}
	
	@Override
	public List<ChannelDefinition> getChannels() {
		return Arrays.<ChannelDefinition>asList(channelUser, channelSuper);
	}
	
	@Override
	public List<ChannelDefinition> getChannels(IOperator user, ChannelPrivilegeInspector privilege) {
		if (user.isSuper()) {
			return Arrays.<ChannelDefinition>asList(channelUser, channelSuper);
		}
		if (user.isAdmin() && privilege.hasPrivilege(channelManager)) {
			return Arrays.<ChannelDefinition>asList(channelUser, channelManager);
		}
		return Collections.<ChannelDefinition>singletonList(channelUser);
	}

	@Override
	public ItemableDefinition find(String mid, String...mids) {
		String tid = platformContext.combine(mid, mids);
		tid = platformContext.idGenerate(tid);
		ItemableDefinition result = idCached.get(tid);
		
		if(null == result) result = tabCached.get(tid);
		if(null == result) result = tabsCached.get(tid);
		return result;
	}

	@Override
	public MenuDefinition menu(String menuId) {
		AbstractMenuable ii = idCached.get(menuId);
		if(ii instanceof MenuDefinition) { return (MenuDefinition)ii; }
		return null;
	}
	
	@Override
	public MenuViewBuilder menuBuilder(MenuCheckable menuCheckable, boolean only, String... key) {
		MenuItemBuilder menuBuilder = MenuItemBuilder.valueOf(platformContext, menuCheckable, only);
		return new MenuViewBuilder(this, menuBuilder).add(key);
	}
	
	@Override
	public ModuleDefinition module(String key) {
		return internalReadModule(key);
	}
	
	private ModuleDefinition internalReadModule(String key){
		if(id.equals(key)) return this;
		
		return moduleCached.get(key);
	}
	
	@Override
	public Collection<TabItemDefinition> tabItems() {
		return tabCached.values();
	}
	
	@Override
	public TabItemDefinition tabItem(String id) {
		return tabCached.get(id);
	}
	
	@Override
	public TabsDefinition tab(String id) {
		return tabsCached.get(id);
	}
	
	@Override
	public AbstractMenuable item(String id) {
		return idCached.get(id);
	}

	@Override
	public ModuleDefinition module() {
		return this;
	}
	
	@Override
	public void setServletContext(ServletContext sc) {
		this.servletContext = sc;
	}
	
	@Override
	public Collection<ModuleDefinition> modules() {
		return new ArrayList<ModuleDefinition>(sortModules);
	}

	@Override
	public String getDefaultEndpoint() {
		return defaultEndpoint;
	}

	@Override
	public List<VersionDefinition> getVersions() {
		return versions;
	}
	
	@Override
	public ChannelDefinitionImpl personalCenter() {
		return channelUser;
	}

	public ServerResourceResolver getResourceServer() {
		return resourceResolver;
	}

	public void setResourceServer(String resourceServer) {
		this.resourceServer = resourceServer;
	}

	public void setLoginPage(LoginPageDefinitionImpl loginPage) {
		this.loginPage = loginPage;
	}
	
	@Override
	public LoginPageDefinition loginPage() {
		return loginPage;
	}
	
	public ChannelDefinitionImpl getChannelManager() {
		return channelManager;
	}
	
	@Override
	public DescribeDefinition describe(String id) {
		return desCached.get(id);
	}
	
	public void setDocumentFolder(String documentFolder) {
		this.documentFolder = documentFolder;
	}
	
//	@Override
//	public ShortcutDefinition shortcut(String id) {
//		return (ShortcutDefinition)idCached.get(id);
//	}
	
//	@Override
//	public StyleDefinitionImpl getStyle(){
//		return style;
//	}
	
//	@Override
//	public DescribeDefinitionImpl getDes() {
//		return des;
//	}

//	@Override
//	public String getEndpoint() {
//		return endpoint;
//	}
	
//	public void setEndpoint(String endpoint) {
//		this.endpoint = endpoint;
//	}
	
//	@Override
//	public final String getId() {
//		return id;
//	}
//
//	public final void setId(String id) {
//		this.id = id;
//	}
//	
//	@Override
//	public final String getName() {
//		return name;
//	}
//
//	public final void setName(String name) {
//		this.name = name;
//	}

//	public void setDes(DescribeDefinitionImpl des) {
//		this.des = des;
//	}

//	public String getPath() {
//		return path;
//	}

//	public void setPath(String path) {
//		this.path = path;
//	}

//	public void setStyle(StyleDefinitionImpl style) {
//		this.style = style;
//	}

//	public String getThumbnail() {
//		return style.getThumbnail();
//	}
	
//	public void setParts(List<PartDefinition> parts) {
//		this.parts = parts;
//	}
//	
//	@Override
//	public List<PartDefinition> getParts() {
//		return parts;
//	}

//	public String getInternalId() {
//		return internalId;
//	}

}
